begin
  require 'amqp'
rescue LoadError
  $stderr.puts "Missing amqp gem. Please add it to Gemfile and run 'bundle install'."
  exit 1
end

require 'fiber'

module Skit
  module Health
    module Check
      class AMQP
        class << self
          def check!(app_name, timeout: 5)
            alive? do |connection|
              new(connection, app_name, timeout).check_queue!
            end
          end

          def alive?
            connection = ::AMQP.connection

            if !connection || connection.closed? || connection.closing?
              raise ::AMQP::ConnectionClosedError.new(nil)
            end

            yield connection if block_given?
          end
        end

        def initialize(connection, app_name, timeout)
          @connection = connection
          @app_name = app_name
          @timeout = timeout
          @timeout_timer = nil
        end

        def check_queue!
          q = queue(channel)

          publish_test_message(q)

          result = blocking do |fiber|
            subscribe_to_test_message(q, fiber)
          end

          parse(result)
        end

        private

        def channel
          ::AMQP::Channel.new(@connection).tap do |channel|
            channel.auto_recovery = true
          end
        end

        def queue_name
          "skit.health.#{@app_name}"
        end

        def queue(channel)
          channel.queue(queue_name)
        end

        def publish_test_message(queue)
          queue.publish(Time.now.to_s)
        end

        def subscribe_to_test_message(queue, fiber)
          queue.subscribe do |payload|
            if payload
              fiber.resume(payload) if fiber.alive?
              EM.cancel_timer(@timeout_timer) if @timeout_timer
              @timeout_timer = nil
              queue.unsubscribe
            end
          end
        end

        def blocking
          f = Fiber.current
          yield f
          with_timeout(f) { Fiber.yield }
        end

        def with_timeout(fiber)
          @timeout_timer = EM.add_timer(@timeout) do
            fiber.resume(:timeout) if fiber.alive?
          end
          yield
        end

        def parse(result)
          raise ::AMQP::Error.new("Nothing found in #{queue_name}") if result == :timeout
        end
      end
    end
  end
end
